#pragma once
#include "CountRange.h"

namespace boolinq
{
    #define unknown_size -1

    template<typename R, typename F> 
    class Where_iRange
    {
    public:
        typedef typename R::value_type value_type;
        
        Where_iRange(R r, F f)
            : r(r)
            , f(f)
            , frontReady(false)
            , backReady(false)
            , frontIndex(0)
            , backIndex(0)
            , length(unknown_size)
            , baseRange(r)
        {
        }

        bool empty() 
        { 
            if (!frontReady)
                seekFront();
            return r.empty();
        }

        value_type popFront() 
        { 
            if (!frontReady)
                seekFront();

            auto tmp = *this;
            r.popFront();
            frontReady = false;
            return tmp.front();
        }

        value_type popBack() 
        {
            if (!backReady)
                seekBack();

            auto tmp = *this;
            r.popBack();
            backReady = false;
            return tmp.back();
        }

        value_type front()
        { 
            if (!frontReady)
                seekFront();
            return r.front();
        }

        value_type back() 
        { 
            if (!backReady)
                seekBack();
            return r.back();
        }

    private:
        void seekFront()
        {
            while(!r.empty() && !f(r.front(), frontIndex++))
                r.popFront();
            frontReady = true;
        }

        void seekBack()
        {
            CalculateBackIndexIfNeeded();                

            while(!r.empty() && !f(r.back(), backIndex--))
                r.popBack();
            backReady = true;
        }
        
        virtual void CalculateBackIndexIfNeeded()
        {
            if( length != unknown_size ){ return; }

            //count length from base (!!) range because begin iterator of current range can be modified (e.g. because of previous call of r.front() method)
            length = CountRange( baseRange );
            backIndex = length - 1;
        }

    private:
        R r;
        F f;

        bool frontReady;
        bool backReady;

        int frontIndex;
        int backIndex;

        R baseRange;
        int length;
    };

    template<typename F>
    class BinaryPredicatAdapter
    {
    public:
        BinaryPredicatAdapter( F f )
            : f(f){}

        template<typename TArg1, typename TArg2>
        bool operator()( const TArg1& t1, const TArg2& )
        {
            return f(t1);
        }
    private:
        F f;
    };

    template<typename R, typename F> 
    class WhereRange
        : public Where_iRange< R, BinaryPredicatAdapter<F> >
    {
    public:
        WhereRange(R r, F f)
            : Where_iRange(r, BinaryPredicatAdapter<F>(f)){}
    
    private:
        virtual void CalculateBackIndexIfNeeded(){}
    };

    // where(where(xxx, ...), ...)

    template<typename R, typename F>
    WhereRange<R, F> where(R r, F f)
    {
        return WhereRange<R, F>(r,f);
    }

    // where_i(where(xxx, ...), ...)

    template<typename R, typename F>
    Where_iRange<R,F> where_i(R r, F f)
    {
        return Where_iRange<R, F>(r,f);
    }
    
    // xxx.where(...).where(...)
    // xxx.where_i(...).where_i(...)

    template<template<typename> class TLinq, typename R>
    class WhereRange_mixin
    {
    public:
        template<typename F>
        TLinq< WhereRange< R, F > > where(F f) const
        {
            return boolinq::where(((TLinq<R>*)this)->r,f);
        }

        template<typename F>
        TLinq< Where_iRange<R,F> > where_i(F f) const
        {
            return boolinq::where_i(((TLinq<R>*)this)->r,f);
        }
    };
}
// namespace boolinq
